package mysql

import (
	"fmt"
	"io"
	"strings"
	"text/template"

	"gitee.com/lipore/plume/db_gorm/gen/code"
	"gitee.com/lipore/plume/db_gorm/gen/spec"
)

type findMethodData struct {
	repositoryMethodData
	Entity       code.Type
	DomainObject code.Type
	Query        string
	Ordering     string
}

var findMethodTemplate = `
{{block "repositoryMethod" .}}{{end}} {
    entities := make([]{{typeToCode .Entity}}, 0)
	domainObjects := make([]{{typeToCode .DomainObject}}, 0)
    conn, err := db_gorm.LoadTx(ctx)
    if err != nil {
        return nil, err
    }
	err = conn{{.Query}}{{.Ordering}}.Find(&entities).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return domainObjects, nil
		}
		return nil, err
	}
	for _, e := range entities {
		domainObjects = append(domainObjects, e.toDomainObject())
	}
	return domainObjects, nil
}`

var findOneMethodTemplate = `
{{block "repositoryMethod" .}}{{end}} {
    e := &{{typeToCode .Entity}} {}
    conn, err := db_gorm.LoadTx(ctx)
    if err != nil {
        return nil, err
    }
	err = conn{{.Query}}{{.Ordering}}.First(&e).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return e.toDomainObject(), nil
}`

func renderFindMethod(s spec.MethodSpec, entity code.Type, repository code.Type, operation spec.FindOperation, writer io.Writer) error {
	tmpl := template.New("find Method")
	tmpl, err := renderRepositoryMethod(tmpl)
	if err != nil {
		return err
	}

	if operation.Mode == spec.QueryModeOne {
		tmpl, err = tmpl.Parse(findOneMethodTemplate)
	} else {
		tmpl, err = tmpl.Parse(findMethodTemplate)
	}
	if err != nil {
		return err
	}

	tmpl, err = tmpl.Parse(errorHandleReturnTemplate)
	if err != nil {
		return err
	}

	query, err := renderQueryStmt(s, operation.Query)
	var domainObject code.Type
	if retType, ok := s.Returns[0].(code.ArrayType); ok {
		domainObject = retType.ContainedType
	} else if retType, ok := s.Returns[0].(code.PointerType); ok {
		domainObject = retType.ContainedType
	}
	if err != nil {
		return err
	}
	tmplData := findMethodData{
		repositoryMethodData: repositoryMethodData{Receiver: code.Param{Name: "repository", Type: code.PointerType{ContainedType: repository}}, Spec: s},
		Entity:               entity,
		DomainObject:         domainObject,
		Query:                query,
		Ordering:             renderOder(operation.Sorts),
	}
	return tmpl.Execute(writer, tmplData)
}

func renderOder(sorts []spec.Sort) string {
	sortStrs := make([]string, len(sorts))
	for i, s := range sorts {
		sortStrs[i] = fmt.Sprintf(".Order(\"%s %s\")", fieldToColumn(s.FieldReference.ReferencedField()), s.Ordering)
	}
	return strings.Join(sortStrs, "")
}

type countMethodData struct {
	repositoryMethodData
	Entity code.Type
	Query  string
}

var countMethodTemplate = `
{{block "repositoryMethod" .}}{{end}} {
    var count int64
    conn, err := db_gorm.LoadTx(ctx)
    if err != nil {
        return 0, err
    }
	err = conn.Model(&{{typeToCode .Entity}}{}){{.Query}}.Count(&count).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return 0, nil
		}
		return 0, err
	}
	return count, nil
}`

func renderCountMethod(s spec.MethodSpec, entity code.Type, repository code.Type, operation spec.CountOperation, writer io.Writer) error {
	tmpl := template.New("count Method")
	tmpl, err := renderRepositoryMethod(tmpl)
	if err != nil {
		return err
	}

	tmpl, err = tmpl.Parse(countMethodTemplate)
	if err != nil {
		return err
	}

	query, err := renderQueryStmt(s, operation.Query)
	if err != nil {
		return err
	}
	tmplData := countMethodData{
		repositoryMethodData: repositoryMethodData{Receiver: code.Param{Name: "repository", Type: code.PointerType{ContainedType: repository}}, Spec: s},
		Entity:               entity,
		Query:                query,
	}
	return tmpl.Execute(writer, tmplData)
}
